/*
    This file is part of Epoch <http://code.google.com/p/epoch/>.
    
    Copyright (C) 2011  Adam Meily <meily.adam@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/


#include "SqlRecordStore.h"
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlRecord>
#include <QtSql/QSqlField>

SqlRecordStore::SqlRecordStore(const DatabaseConfig& config, const QString& id)
{
    m_backend = QSqlDatabase::addDatabase("QMYSQL", id);
    m_backend.setHostName(config.hostname());
    m_backend.setPort(config.port());
    m_backend.setUserName(config.username());
    m_backend.setPassword(config.password());
    m_backend.setDatabaseName(config.database());
}


SqlRecordStore::~SqlRecordStore()
{
}

int SqlRecordStore::open()
{
    return m_backend.open() ? 0 : -1;
}

const QString& SqlRecordStore::id() const
{
    return m_id;
}

const QSqlDatabase& SqlRecordStore::backend() const
{
    return m_backend;
}


Device SqlRecordStore::device(const QByteArray& uuid)
{
    if(!m_backend.isOpen())
        return Device();
    
    QSqlQuery query(m_backend);
    bool ok = query.exec(
        QString("SELECT * FROM `%0` WHERE uuid='%1'")
            .arg(m_config.deviceTable())
            .arg(uuid.toHex())
    );
    
    if(!ok || !query.first())
        return Device();
    
    QSqlRecord record = query.record();
    
    Device device = toDevice(record, shares);
    if(device.isNull())
        return Device();
    
    loadShares(device);
    return device;
}

Device SqlRecordStore::toDevice(const QSqlRecord& record) const
{
    bool ok;
    int id = record.value("id").toInt(&ok);
    if(!ok || id < 0)
        return Device();
    
    QByteArray uuid, key;
    QString uuidStr, keyStr;
    uuidStr = record.value("uuid").toString();
    keyStr = record.value("device_key").toString();
    
    uuid = QByteArray::fromHex(uuidStr.toUtf8());
    key = QByteArray::fromHex(keyStr.toUtf8());
    
    if(uuid.isEmpty() || key.isEmpty())
        return Device();
    
    return Device(id, uuid, key);
}


void SqlRecordStore::loadShares(Device& device)
{
    QList<Share> shares = this->shares(device.id());
    device.inject(shares);
}

QList< Share > SqlRecordStore::shares(int deviceId)
{
    QList<Share> list;
    
    QSqlQuery query(m_backend);
    bool ok = query.exec(
        QString("SELECT * FROM `%0` WHERE device_id=%1")
            .arg(m_config.shareTable())
            .arg(deviceId)
    );
    
    if(!ok)
        return list;
    
    while(query.next())
    {
        Share share = toShare(query.record());
        if(!share.isNull())
            list.append(share);
    }
    
    return list;
}

Share SqlRecordStore::toShare(const QSqlRecord& record, const Device& device) const
{
    bool ok;
    int id = record.value("id").toInt(&ok);
    if(!ok || id < 0)
        return Share();
    
    QString path = record.value("path").toString();
    QString uuidStr = record.value("uuid").toString();
    QString name = record.value("name").toString();
    QByteArray uuid = QByteArray::fromHex(uuidStr.toUtf8());
    
    return Share(id, device, uuid, path, name);
}

Revision SqlRecordStore::latestRevision(const QByteArray& uuid, const Share& share)
{
    if(!m_backend.isOpen())
        return Revision();
    
    QSqlQuery query(m_backend);
    bool ok = query.exec(
        QString("SELECT * FROM `%0` WHERE uuid=`%1` AND share_id=%2")
            .arg(m_config.revisionTable())
            .arg(uuid.toHex())
            .arg(share.id())
    );
    
    if(!ok || !query.first())
        return Revision();
    
    QSqlRecord record = query.record();
    Revision rev = toRevision(record, share);
    if(rev.isNull())
        return rev;
    
    if(!record.isNull("basis_id"))
    {
        Revision basis = revision(record.value("basis_id").toInt(), share);
        rev.inject(basis);
    }
    
    return rev;
}

QList< Revision > SqlRecordStore::allRevisions(const Revision& revision)
{
    QList<Revision> list = allRevisions(revision.uuid(), revision.share());
    QMutableListIterator<Revision> i(list);
    while(i.hasNext())
    {
        if(i.next().id() == revision.id())
            i.remove();
    }
    
    return list;
}

QList< Revision > SqlRecordStore::allRevisions(const QByteArray& uuid,
                                               const Share& share)
{
    QList<Revision> list;
    QMap<int, int> cache;
    if(!m_backend.isOpen())
        return list;
    
    QSqlQuery query(m_backend);
    bool ok = query.exec(
        QString("SELECT * FROM `%0` WHERE uuid='%1' AND share_id=%2")
            .arg(m_config.revisionTable())
            .arg(uuid.toHex())
            .arg(share.id())
    );
    
    if(!ok)
        return list;
    
    while(query.next())
    {
        QSqlRecord record = query.record();
        Revision rev = toRevision(record, share);
        if(!rev.isNull())
        {
            if(!record.isNull("basis_id"))
            {
                int id = record.value("basis_id").toInt();
                if(cache.contains(id))
                {
                    int index = cache[id];
                    rev.inject(list[index]);
                }
                else
                {
                    Revision basis = this->revision(record.value("basis_id").toInt(),
                                                    share);
                    if(!basis.isNull())
                        rev.inject(basis);
                }
            }
            
            list.append(rev);
            cache[rev.id()] = list.length() - 1;
        }
    }
    
    return list;
}


Revision SqlRecordStore::revision(int id, const Share& share)
{
    if(!m_backend.isOpen())
        return Revision();
    
    QSqlQuery query(m_backend);
    bool ok = query.exec(
        QString("SELECT * FROM `%0` WHERE id=%1 AND share_id=%2")
            .arg(m_config.revisionTable())
            .arg(id)
            .arg(share.id())
    );
    
    if(!ok || !query.first())
        return Revision();
    
    QSqlRecord record = query.record();
    Revision rev = toRevision(record, share);
    
    if(rev.isNull())
        return rev;
    
    if(!record.isNull("basis_id"))
    {
        Revision basis = revision(record.value("basis_id").toInt(), share);
        rev.inject(basis);
    }
    
    return rev;
}

Revision SqlRecordStore::toRevision(const QSqlRecord& record, const Share& share) const
{
    bool ok;
    quint64 size;
    qint64 seenEpoch, backupEpoch;
    int rev;
    QString path;
    QDateTime backup, seen;
    QString uuidStr;
    QByteArray uuid;
    int id;
    
    id = record.value("id").toInt(&ok);
    if(!ok || id < 0)
        return Revision();
    
    path = record.value("path").toString();
    if(path.isEmpty())
        return Revision();
    
    size = record.value("size").toULongLong(&ok);
    if(!ok)
        return Revision();
    
    seenEpoch = record.value("last_seen").toLongLong(&ok);
    if(!ok || (seen = QDateTime::fromMSecsSinceEpoch(seenEpoch)).isNull())
        return Revision();
    
    backupEpoch = record.value("backup_time").toLongLong(&ok);
    if(!ok || (backup = QDateTime::fromMSecsSinceEpoch(backupEpoch)).isNull())
        return Revision();
    
    rev = record.value("revision").toInt(&ok);
    if(!ok || rev < 1)
        return Revision();
    
    uuidStr = record.value("uuid").toString();
    uuid = QByteArray::fromHex(uuidStr.toUtf8());
    if(uuid.isEmpty())
        return Revision();
    
    return Revision(id, uuid, share, size, seen, backup, path, rev);
}

int SqlRecordStore::save(Revision& revision, const Backup& backup)
{
    if(!revision.isDirty() || revision.isNull())
        return 1;
    
    return 0;
}







/*
Record SqlRecordStore::toRecord(const QSqlRecord& sqlrec, const DatabaseTableConfig& config) const
{
    Record record;
    int len = sqlrec.count();
    for(int i = 0; i < len; i++)
    {
        QSqlField field = sqlrec.field(i);
        QString col = config.reverseCol(field.name());
        
        record[col] = field.value().toString();
    }
    
    return record;
}



RemoteDevice SqlRecordStore::device(const QByteArray& uuid)
{
    if(!m_backend.isOpen())
        return RemoteDevice();
}

QList< ShareFileRevision > SqlRecordStore::allRevisions(const QByteArray& uuid)
{
    QList<ShareFileRevision> list;
    if(!m_backend.isOpen())
        return list;
    
    const DatabaseTableConfig& tbl = m_config.tables["share-file-reivision"];
    QSqlQuery query(m_backend);
    query.exec(
        QString("SELECT * FROM %0 WHERE %1='%2'")
            .arg(tbl.table())
            .arg(tbl.col("uuid"))
            .arg(uuid.toHex())
    );
    
    while(query.next())
    {
        QSqlRecord rec = query.record();
        ShareFileRevision rev = ShareFileRevision::fromRecord(toRecord(rec, tbl));
        if(!rev.isNull())
            list.append(rev);
    }
    
    return list;
}

ShareFileRevision SqlRecordStore::latestBasis(const QByteArray& uuid)
{
    if(!m_backend.isOpen())
        return ShareFileRevision();
    
    const DatabaseTableConfig& tbl = m_config.tables["share-file-revision"];
    QSqlQuery query(m_backend);
    query.exec(
        QString("SELECT * FROM %0 WHERE %1='%2' AND %3=NULL ORDER BY %4 DESC LIMIT 1")
            .arg(tbl.table())
            .arg(tbl.col("uuid"))
            .arg(uuid.toHex())
            .arg(tbl.col("basis-id"))
            .arg(tbl.col("revision"))
    );
    query.first();
    
    QSqlRecord rec = query.record();
    return ShareFileRevision::fromRecord(toRecord(rec, tbl));
}

ShareFileRevision SqlRecordStore::revision(QSqlRecord& record, QMap< int, ShareFileRevision >& cache)
{
    const DatabaseTableConfig& tbl = m_config.tables["share-file-revision"];
    QSqlField field = record.field(tbl.col("basis-id"));
    if(field.isNull())
        return ShareFileRevision::fromRecord(toRecord(record, tbl));
    
    int id = field.value().toInt();
    if(cache.contains(id))
        return ShareFileRevision::fromRecord(toRecord(record, tbl), cache[id]);
}


int SqlRecordStore::save(ShareFileRevision& revision)
{
    if(!revision.isDirty() || revision.isNull())
        return 1;
    
    
    return 0;
}
*/
